/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */
package cgl.narada.transport.tcp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.StringTokenizer;

import cgl.narada.transport.Link;
import cgl.narada.transport.LinkFactory;
import cgl.narada.transport.TransportDebugFlags;
import cgl.narada.transport.TransportException;
import cgl.narada.transport.TransportHandler;

/**
 * This is a TCP implementation of the LinkFactory Interface. Lets assume that
 * the connection is being initiated by a node A to another node B. There are
 * two independent factors that need to borne in mind while using LinkFactory.
 * The first is of course setting the link factory properties. The second is
 * specifying the properties to create a link to some other node B.
 * 
 * <p>
 * To initialize the TCPLinkFactory one needs to specify the <b>TCPServerPort
 * </b>. When the value of this variable is set to <b>0</b> it implies that the
 * node A initiating a connection to node B will not accept link creation
 * requests from any other node.
 * <p>
 * To create a link to node B. The following properties needs to be specified.
 * <li><b>hostname</b>: This is the hostname on which the node B's process is
 * running.
 * <li><b>portnum</b>: This is the port number on which node B accepts link
 * creation requests from other nodes. In other words, the <i>TCPServerPort </i>
 * specified to the TCPChannelFactory at node B is equal to portnum.
 * 
 * @author Shrideep Pallickara $Date$ $Revision$
 */

public class TCPLinkFactory implements LinkFactory, TransportDebugFlags {

    private String managedLinkType = "tcp";

    private Properties properties;

    private String moduleName = "TCPLinkFactory: ";

    private int tcpServerSocketPort = 0;

    private TCPServerThread tcpServerThread;

    private Hashtable links;

    private TransportHandler transportHandler;

    public TCPLinkFactory() {
        links = new Hashtable();
    }

    /*
     * These are the set of properties that are needed to set up the link
     * Factory.
     */
    public void setLinkFactoryProperties(Properties factoryProperties) {
        if (factoryProperties == null) {
            System.out.println(moduleName + "The specified properties is NULL");
            return;
        }
        properties = factoryProperties;
        
        String _valueString = properties.getProperty("TCPServerPort");
        if (_valueString != null) {
            tcpServerSocketPort = Integer.parseInt(_valueString);
        } else {
            if (TCPLinkFactory_Debug) {
                System.out.println(moduleName
                    + "Property TCPServerPort missing. "
                    + "So this is a client");
            }
        }
    }

    /** Return the type of all the links created/managed by this factory */
    public String getManagedLinksType() {
        return managedLinkType;
    }

    /**
     * Create a communication link based on the properties that have been
     * specified.
     */
    public Link createLink(Properties linkProperties) throws TransportException {
        if (linkProperties == null) {
            throw new TransportException(moduleName
                + "The specified link propertis is null");
        }

        String _hostName = linkProperties.getProperty("hostname");
        String _portString = linkProperties.getProperty("portnum");

        if (_hostName == null || _portString == null) {
            throw new TransportException(moduleName
                + "One of the specified properties is null");
        }
        int _portNum = Integer.parseInt(_portString);

        try {
            InetAddress _ia = InetAddress.getByName(_hostName);
            _hostName = _ia.getHostAddress();
            String _key = "tcp://" + _hostName + ":" + _portNum;

            if (links.containsKey(_key)) {
                throw new TransportException(moduleName
                    + "Link already exists to " + _key);
            }

            // Socket socket = new Socket(_ia, _portNum);
            Socket socket = new Socket();
            socket.setReuseAddress(true);
            socket.setKeepAlive(true);

            InetSocketAddress endpoint = new InetSocketAddress(_hostName,
                                                               _portNum);
            socket.connect(endpoint);
            
            TCPLink _link = new TCPLink(socket, this);
            _link.setLinkId(_key);

            links.put(_key, _link);
            transportHandler.registerLink(this, _link);
            _link.startLinkServices();
            return _link;
        } catch (UnknownHostException uhe) {
            String errorInfo = moduleName + "Unknown host [" + _hostName + "]"
                + uhe;
            System.out.println(errorInfo);
            throw new TransportException(errorInfo);
        } catch (IOException ioe) {
            String errorInfo = moduleName + " Unable to Setup Connection to ["
                + _hostName + ":" + _portNum + "] => " + ioe;
            System.out.println(errorInfo);
            throw new TransportException(errorInfo);
        }
    }

    /**
     * Set the interval for checking status of all links managed by this factory
     * to the one specified in the argument
     */
    public void setLinkStatusInterval(long interval) {

    }

    /**
     * Start communication services. In the case of TCP for example, the thread
     * which incorporates the serverSocket.accept() is started
     */
    public boolean loadLinkFactoryServices() throws TransportException {
        if (tcpServerSocketPort == 0) {
            /**
             * This implies that there is no need to start up a TCPServerSocket
             * This is true in the case of clients, who don't anticipate
             * connections from other clients e.g. JMS Clients
             */
            if (TCPLinkFactory_Debug) {
                System.out.println(moduleName
                    + "Will not be starting the ServerSocket"
                    + " Thread since the port specified is [0]");
            }
            return true;
        }

        tcpServerThread = new TCPServerThread(tcpServerSocketPort, this);
        tcpServerThread.start();
        System.out.println("Loaded TCPLinkFactory communication services");
        return true;
    }

    /**
     * Indicates if this factory can manage migration of links of a certain type
     */
    public boolean canManageLinkMigrationOfType(String linkType) {
        /** NBNI */
        return false;
    }

    /**
     * Used to migrate communications of a link to the one supported by links
     * managed by this factory
     */
    public void manageLinkMigration(Link link) throws TransportException {
        /** NBNI */
    }

    /**
     * Sets the reference to the transport handler. This is done during the
     * loadLinkFactory() operation within the transport handler implementations.
     */
    public void setTransportHandler(TransportHandler transportHandler) {
        this.transportHandler = transportHandler;
    }

    /**
     * This method is used to garbage collect any resources associated with the
     * link factory
     */
    public void dispose() {
        Enumeration _links = links.elements();
        while (_links.hasMoreElements()) {
            TCPLink _tcpLink = (TCPLink) _links.nextElement();
            _tcpLink.closeLink();
        }

        if (tcpServerThread != null) {
            if (TCPLinkFactory_Debug) {
                System.out.println(moduleName + "Disposing TCPServerThread");
            }
            tcpServerThread.closeServerSocket();
            tcpServerThread = null;
        }
    }

    public void handleInboundTCPLinkRequest(Socket socket) {
        if (TCPLinkFactory_Debug) {
            System.out.println(moduleName
                + "InboundConnection request received " + socket);
        }

        try {
            socket.setReuseAddress(true);
            socket.setKeepAlive(true);
        } catch (SocketException e) {
            System.out.println(moduleName
                + "Problems enabling [keepAlive] OR [ReuseAddress]" + e);
        }

        // String _hostName = socket.getInetAddress().getHostName();
        String _hostName = socket.getInetAddress().getHostAddress();
        int _portNum = socket.getPort();
        String _linkId = "tcp://" + _hostName + ":" + _portNum;
        if (links.containsKey(_linkId)) {
            System.out.println(moduleName + "InboundConnection => "
                + "Link aleady exists to " + _linkId);
        }

        try {
            TCPLink _tcpLink = new TCPLink(socket, this);
            _tcpLink.setLinkId(_linkId);
            links.put(_linkId, _tcpLink);
            transportHandler.registerLink(this, _tcpLink);
            _tcpLink.startLinkServices();
        } catch (TransportException transEx) {
            System.out.println("moduleName" + transEx);
        } /* end try-catch */
    }

    public void reportLinkLoss(Link linkLost) {
        String _linkId = linkLost.getLinkId();
        if (_linkId == null) {
            System.out.println(moduleName + "ReportLinkLoss Link Id is null");
            return;
        }
        if (links.containsKey(_linkId)) {
            transportHandler.manageLinkLoss((Link) links.remove(_linkId));
            System.out.println(moduleName + "Managed Link Loss");
            return;
        }

        System.out.println(moduleName + "Unaware of link [" + _linkId
            + "] whose connection loss is being reported");
    }

    public TransportHandler getTransportHandler() {
        if (transportHandler == null)
            System.out.println(moduleName
                + "TransportHandler reference is NULL");
        return transportHandler;
    }

    public static void main(String[] args) {
        TCPLinkFactory factory = new TCPLinkFactory();
        Properties props = new Properties();
        props.put("TCPServerPort", args[0]);
        factory.setLinkFactoryProperties(props);
        BufferedReader r = new BufferedReader(new InputStreamReader(System.in));
        String cmd;
        StringTokenizer tok;
        Hashtable links = new Hashtable();
        try {
            factory.loadLinkFactoryServices();
            System.out.println("\nType h for usage options");
            while (true) {
                cmd = r.readLine();

                if (cmd == null) {
                    factory.dispose();
                    break;
                }

                if (cmd.startsWith("h")) {
                    System.out
                        .println("cr <host> <port> \t \t \t Creates a link");
                    System.out.println("cl <link-id> \t \t \t Closes a link");
                    continue;
                }

                if (cmd.startsWith("cr")) {
                    tok = new StringTokenizer(cmd, " ");
                    if (tok.countTokens() != 3) {
                        System.out
                            .println("cr <host> <port> \t \t \t Creates a link");
                        continue;
                    }
                    Properties _props = new Properties();
                    tok.nextToken();
                    _props.put("hostname", tok.nextToken());
                    _props.put("portnum", tok.nextToken());
                    Link _link = factory.createLink(_props);
                    links.put(_link.getLinkId(), _link);
                    System.out.println("Created link with id "
                        + _link.getLinkId());
                }
                /** if (creation of link */

                if (cmd.startsWith("cl")) {
                    tok = new StringTokenizer(cmd, " ");
                    if (tok.countTokens() != 2) {
                        System.out
                            .println("cl <link-id> \t \t \t Closes a link");
                        continue;
                    }
                    tok.nextToken();
                    String _linkId = tok.nextToken();
                    if (links.containsKey(_linkId)) {
                        Link _link = (Link) links.remove(_linkId);
                        _link.closeLink();
                    } else {
                        System.out.println("Specified link Id is not correct!");
                    }
                } /* if (closing of a specified link ) */

                if (cmd.equalsIgnoreCase("q")) {
                    factory.dispose();
                    break;
                }
            }/* end while(true) */
        } catch (IOException ioe) {
            ioe.printStackTrace();
        } catch (TransportException transEx) {
            System.out.println(transEx);
        }/* end try-catch */
        System.out.println("TCPLinkFactory: Exiting program ... end main()");
    }/* end-main */

}
